package com.atguigu.gmall.seckill.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.seckill.mapper.SeckillOrderMapper;
import com.atguigu.gmall.seckill.pojo.SeckillOrder;
import com.atguigu.gmall.seckill.pojo.UserRecode;
import com.atguigu.gmall.seckill.service.SeckillOrderService;
import com.atguigu.gmall.seckill.util.DateUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * projectName: gmall
 *
 * @author: 陈鹏
 * time: 2022/10/17 20:46 星期一
 * description: 秒杀订单的服务层
 */
@Service
@Log4j2
@Transactional(rollbackFor = Exception.class)
public class SeckillOrderServiceImpl implements SeckillOrderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;


    /**
     * 秒杀下单:同步排队 异步发消息至RabbitMQ
     *  @param time
     * @param goodsId
     * @param num
     * @return
     */
    @Override
    public UserRecode addSeckillOrder(String time, String goodsId, Integer num) {
        String username = "cp";
        UserRecode userRecode = new UserRecode();
        userRecode.setUsername(username);
        userRecode.setCreateTime(new Date());
        userRecode.setGoodsId(goodsId);
        userRecode.setTime(time);
        userRecode.setNum(num);
        //防止重复排队
        Long increment = redisTemplate.opsForValue().increment("User_Queue_Count_" + username, 1);
        if(increment > 1){
            //重复排队
            userRecode.setStatus(3);
            userRecode.setMsg("重复排队,秒杀失败!");
            return userRecode;
        }
        //排队成功: 记录谁 要买什么东西 买哪个时间段的 买几个
        userRecode.setStatus(1);
        userRecode.setMsg("排队中");
        //将用户的排队信息存储到redis中去
        CompletableFuture.runAsync(()->{
           redisTemplate.opsForValue().set("User_Record_Status_" + username,userRecode);
            //发送消息
            rabbitTemplate.convertAndSend("seckill_order_exchange",
                    "seckill.order",
                    JSONObject.toJSONString(userRecode));
        },threadPoolExecutor);
        //返回
        return userRecode;
    }

    /**
     * 查询用户的排队状态
     *
     * @return
     */
    @Override
    public UserRecode getUserRecode() {
        String username = "cp";
        return (UserRecode) redisTemplate.opsForValue().get("User_Record_Status_" + username);
    }

    /**
     * 真实秒杀下单的方法
     *
     * @param userRecode
     */
    @Override
    public void realSeckillOrderAdd(UserRecode userRecode) {
        //获取用户名
        String username = userRecode.getUsername();

        //获取时间段
        String time = userRecode.getTime();
        //判断是否在当前活动时间以内
        String nowTime =
                DateUtil.data2str(DateUtil.getDateMenus().get(0), DateUtil.PATTERN_YYYYMMDDHH);
        if(!nowTime.equals(time)){
            //不在活动时间以内,秒杀失败, 结束
            setFail("不在活动时间以内", userRecode);
            //结束
            return;
        }

        //获取商品id
        String goodsId = userRecode.getGoodsId();
        SeckillGoods seckillGoods =
                (SeckillGoods) redisTemplate.opsForHash().get(time, goodsId);
        //商品不存在(time内没有这个商品)!
        if(seckillGoods == null ||
                seckillGoods.getId() == null){
            //商品不存在,秒杀失败, 结束
            setFail("商品不存在", userRecode);
            //结束
            return;
        }

        //获取购买的数量
        Integer num = userRecode.getNum();
        if(num <= 0 || seckillGoods.getSeckillLimit() < num ){
            setFail("超出秒杀限购", userRecode);
            //结束
            return;
        }

        //redis中的库存
        //方案一: 循环pop,缺点: 需要一次次pop操作,太频繁
        for (int i = 0; i < num; i++) {
            //从该商品的队列中进行出列操作,出列的元素不为空代表库存还有,为空代表库存没了
            Object o = redisTemplate.opsForList().rightPop("Seckill_Goods_Queue_" + goodsId);
            if(o == null){
                //回滚
                if(i > 0){
                    redisTemplate.opsForList().leftPushAll("Seckill_Goods_Queue_" +  goodsId, getIds(goodsId, i));
                }
                setFail("商品库存不足",userRecode);
                //结束
                return;
            }
        }

        //方案二: 商品库存需要重新进行整理(List数据就可以淘汰了)
       /* Long increment = redisTemplate.opsForHash().increment("Seckill_Goods_Stock_" + time,
                goodsId,
                -num);
        if(increment.intValue() < 0) {
            //回滚库存
            redisTemplate.opsForHash().increment("Seckill_Goods_Stock_" + time, goodsId, num);
            //库存不足失败
            setFail("商品库存不足", userRecode);
            //结束
            return;
        }*/

        //更新库存
        Long increment = redisTemplate.opsForHash().increment("Seckill_Goods_Stock_" + time,
                goodsId,
                -num);

        seckillGoods.setStockCount(increment.intValue());
        redisTemplate.opsForHash().put(time,goodsId,seckillGoods);
        //生成秒杀订单
        SeckillOrder seckillOrder = new SeckillOrder();
        seckillOrder.setId(UUID.randomUUID().toString().replace("-", ""));
        seckillOrder.setGoodsId(goodsId);
        seckillOrder.setNum(userRecode.getNum());
        seckillOrder.setMoney(seckillGoods.getCostPrice().doubleValue() * userRecode.getNum() + "");
        seckillOrder.setCreateTime(new Date());
        seckillOrder.setStatus("0");
        //记录订单到redis
        redisTemplate.opsForHash().put("User_Seckill_Order_" + time, seckillOrder.getId(), seckillOrder);
        //写状态
        userRecode.setStatus(2);
        userRecode.setMsg("秒杀成功,等待支付!");
        userRecode.setOrderId(seckillOrder.getId());
        userRecode.setMoney(seckillOrder.getMoney());
        //写入最新的排队状态
        redisTemplate.opsForValue().set("User_Record_Status_" + username, userRecode);
        //发送延迟消息,防止超时订单不付钱
        rabbitTemplate.convertAndSend("seckill_order_delay_normal_exchange",
                "seckill.order.dead",
                username,
                (message) -> {
                    //获取消息的属性
                    MessageProperties messageProperties = message.getMessageProperties();
                    //设置过期时间
                    messageProperties.setExpiration("900000");
                    //返回
                    return message;
                });
    }

    /**
     * 秒杀失败的统一设置
     * @param msg
     * @param userRecode
     */
    private void setFail(String msg, UserRecode userRecode) {
        //超出限购,秒杀失败, 结束
        userRecode.setStatus(3);
        userRecode.setMsg(msg + ",秒杀失败!");
        //写到redis中去
        redisTemplate.opsForValue().set("User_Record_Status_" + userRecode.getUsername(), userRecode);
        //删除用户的排队计数器
        redisTemplate.delete("User_Queue_Count_" + userRecode.getUsername());
    }

    @Resource
    private SeckillOrderMapper seckillOrderMapper;
    /**
     * 取消秒杀订单
     *
     * @param username
     */
    @Override
    public void cancelSeckillOrder(String username) {
        //关闭交易 --TODO

        String status = "3";
        //判断参数中的username是否为空
        if(StringUtils.isEmpty(username)){
            //主动取消
            username = "cp";
        }else {
            //超时取消
            status = "4";
        }
        //从redis中获取用户的排队状态
        UserRecode userRecode =
                (UserRecode) redisTemplate.opsForValue().get("User_Record_Status_" + username);
        if(username == null){
            return;
        }
        //获取用户的订单号
        String orderId = userRecode.getOrderId();
        //获取时间段
        String time = userRecode.getTime();
        //获取商品id
        String goodsId = userRecode.getGoodsId();
        //购买的数量
        Integer num = userRecode.getNum();
        //获取redis中的订单数据
        SeckillOrder seckillOrder =
                (SeckillOrder) redisTemplate.opsForHash().get("User_Seckill_Order_" + time, orderId);
        if(seckillOrder == null || seckillOrder.getId() == null || !seckillOrder.getStatus().equals("0")){
            return;
        }
        //将订单的信息存储到数据库中去
        seckillOrder.setStatus(status);
        int insert = seckillOrderMapper.insert(seckillOrder);
        if(insert <= 0){
            log.error("取消订单失败,订单的信息为:" + JSONObject.toJSONString(seckillOrder));
        }
        //后续处理: 1.回滚库存
            rollbackStock(goodsId,num,time);
        //后续处理: 2.清理标识位
        clearFlag(username,time,orderId);
    }

    /**
     * 清理标识位
     * @param username
     * @param time
     * @param orderId
     */
    private void clearFlag(String username, String time, String orderId) {
        //清理排队计数器
        redisTemplate.delete("User_Queue_Count_" + username);
        //清理排队状态
        redisTemplate.delete("User_Record_Status_" + username);
        //清理订单的信息
        redisTemplate.opsForHash().delete("User_Seckill_Order_" + time, orderId);
    }

    /**
     * 回滚库存
     * @param goodsId
     * @param num
     * @param time
     */
    private void rollbackStock(String goodsId, Integer num, String time) {
        //回滚自增值,并获取回滚后剩余几个
        Long increment =
                redisTemplate.opsForHash().increment("Seckill_Goods_Stock_" + time, goodsId, num);
        //从redis中获取商品的数据
        SeckillGoods seckillGoods =
                (SeckillGoods) redisTemplate.opsForHash().get(time, goodsId);
        //商品活动没有结束(商品数据 队列数据)
        if(seckillGoods != null && seckillGoods.getId() != null){
            //回滚商品数据的库存数据
            seckillGoods.setStockCount(increment.intValue());
            redisTemplate.opsForHash().put(time,goodsId,seckillGoods);
            //回滚队列数据
            redisTemplate.opsForList().leftPushAll("Seckill_Goods_Queue_" +  goodsId,getIds(goodsId,num));
        }
    }

    /**
     * 构建一个库存长度的数组
     * @param goodsId
     * @param stockCount
     * @return
     */
    private String[] getIds(String goodsId, Integer stockCount) {
        //初始化一个库存长度的数组
        String[] ids = new String[stockCount];
        //为数组的每个元素赋值
        for (int i = 0; i < stockCount; i++) {
            ids[i] = goodsId;
        }
        //返回
        return ids;
    }

    /**
     * 获取支付结果后修改秒杀订单
     *
     * @param payResult
     */
    @Override
    public void updateSeckillOrder(String payResult) {
        //将支付结果反序列化
        Map<String,String> result = JSONObject.parseObject(payResult, Map.class);
        //附加参数的初始化
        String attachString = "";
        //判断支付渠道
        if(result.get("payway").equals("WX")){
            //微信支付获取附加参数
            attachString = result.get("attach");
        }else{
            //支付宝
            attachString = result.get("支付宝的附加参数的名字");
        }
        //附加参数反序列化
        Map<String, String> attach = JSONObject.parseObject(attachString, Map.class);
        //获取用户名
        String username = attach.get("username");
        //从redis中获取用户的排队状态
        UserRecode userRecode =
                (UserRecode)redisTemplate.opsForValue().get("User_Record_Status_" + username);
        if(userRecode == null){
            return;
        }
        //获取时间段
        String time = userRecode.getTime();
        //获取订单号
        String orderId = userRecode.getOrderId();
        //获取redis中的订单数据
        SeckillOrder seckillOrder =
                (SeckillOrder)redisTemplate.opsForHash().get("User_Seckill_Order_" + time, orderId);
        if(seckillOrder == null ||
                seckillOrder.getId() == null ||
                !seckillOrder.getStatus().equals("0")){
            return;
        }
        //获取订单修改订单的状态为已支付
        seckillOrder.setStatus("1");
        seckillOrder.setPayTime(new Date());
        //将订单的信息存储到数据库中去
        int insert = seckillOrderMapper.insert(seckillOrder);
        if(insert < 0){
            log.error("订单号为:" + orderId + "的订单支付成功,但是保存到数据库失败!");
            return;
        }
        //后续处理: 清理标识位
        clearFlag(username, time, orderId);
    }
}
